﻿using System.ComponentModel;
using System.IO;
using System.Security.Cryptography;
using System.Text;

namespace HashCalculator.ViewModels
{
    class HashModel : NotifyObject
    {
        const int size = 4096;

        BackgroundWorker worker = new BackgroundWorker { WorkerReportsProgress = true, WorkerSupportsCancellation = true };

        public HashModel(HashAlgorithm algorithm, string name)
        {
            Name = name;
            this.Algorithm = algorithm;

            worker.DoWork += (s, e) =>
            {
                e.Result = BytesToStr(ComputeHash(algorithm, e.Argument as string, worker, e));
            };
            worker.ProgressChanged += (s, e) =>
            {
                DonePercent = e.ProgressPercentage;
            };
            worker.RunWorkerCompleted += (s, e) =>
            {
                if (e.Error == null)
                {
                    if (!e.Cancelled)
                    {
                        Result = e.Result as string;
                    }
                }
                IsBusy = false;
            };
        }

        public HashAlgorithm Algorithm { get; private set; }

        public string Name { get; set; }

        string result;

        public string Result
        {
            get
            {
                return result;
            }
            set
            {
                result = value;
                RaisePropertyChanged(() => Result);
            }
        }

        double donePercent;

        public double DonePercent
        {
            get
            {
                return donePercent;
            }
            set
            {
                donePercent = value;
                RaisePropertyChanged(() => DonePercent);
            }
        }

        public void Compute(string filePath)
        {
            Algorithm.Initialize();
            IsBusy = true;
            worker.RunWorkerAsync(filePath);
        }

        public void Cancel()
        {
            IsBusy = false;
            worker.CancelAsync();
        }

        public void Clear()
        {
            Result = null;
            DonePercent = 0;
        }

        bool isBusy = false;

        public bool IsBusy
        {
            get
            {
                return isBusy;
            }
            private set
            {
                isBusy = value;
                RaisePropertyChanged(() => IsBusy);
            }
        }

        string BytesToStr(byte[] bytes)
        {
            StringBuilder str = new StringBuilder();

            for (int i = 0; i < bytes.Length; i++)
            {
                str.AppendFormat("{0:X2}", bytes[i]);
            }
            return str.ToString();
        }

        byte[] ComputeHash(HashAlgorithm hashAlgorithm, string path, BackgroundWorker worker, DoWorkEventArgs e)
        {
            using (FileStream fs = new FileStream(path, FileMode.Open, FileAccess.Read))
            {
                long flength = new FileInfo(path).Length;


                byte[] buffer = new byte[size];
                int count = fs.Read(buffer, 0, size);
                int prevPrecent = 0;
                //worker.ReportProgress(0);
                while (count >= size)
                {
                    if (worker.CancellationPending)
                    {
                        e.Cancel = true;
                        return null;
                    }
                    hashAlgorithm.TransformBlock(buffer, 0, count, buffer, 0);
                    int percent = (int)(fs.Position * 100 / flength);

                    if (percent > prevPrecent + 1)
                    {
                        worker.ReportProgress(percent);
                        prevPrecent = percent;
                    }
                    count = fs.Read(buffer, 0, size);
                }
                worker.ReportProgress(100);
                hashAlgorithm.TransformFinalBlock(buffer, 0, count);

                return hashAlgorithm.Hash;
            }
        }
    }
}
